from datetime import timedelta

from sqlalchemy import and_, select
from sqlalchemy.exc import DBAPIError, IntegrityError
from sqlalchemy.orm import joinedload

from src.core.exceptions.common import RepoError
from src.core.models.dto.subscription import SubscriptionDTO
from src.core.models.enums import SubscriptionStatus
from src.core.utils.date_time import get_date_time
from src.database.converters.subscription import convert_db_model_to_subscription_dto
from src.database.exception_mapper import exception_mapper
from src.database.models import Subscription, User
from src.database.repositories.base import SQLAlchemyRepo


class SubscriptionReaderImpl(SQLAlchemyRepo):
    @exception_mapper
    async def get_subscription_by_id(
        self, subscription_id: int
    ) -> SubscriptionDTO | None:
        """Получаем подписку по id"""

        query = select(Subscription).where(Subscription.id == subscription_id)
        subscription: Subscription | None = await self._session.scalar(query)

        if not subscription:
            return None

        return convert_db_model_to_subscription_dto(subscription)

    @exception_mapper
    async def get_active_user_subscription(
        self, user_tg_id: int
    ) -> SubscriptionDTO | None:
        """Получаем активную подписку пользователя"""

        query = (
            select(Subscription)
            .join(Subscription.user)
            .where(
                and_(
                    User.telegram_id == user_tg_id,
                    Subscription.status == SubscriptionStatus.active,
                )
            )
            .options(joinedload(Subscription.calendars))
        )

        subscription: Subscription | None = await self._session.scalar(query)

        if not subscription:
            return None

        return convert_db_model_to_subscription_dto(subscription, with_calendars=True)

    @exception_mapper
    async def get_subscription_with_activation_code(
        self, user_tg_id: int, activation_code_id: int
    ) -> SubscriptionDTO | None:
        """Получаем подписку пользователя с кодом активации"""

        query = (
            select(Subscription)
            .join(Subscription.user)
            .where(
                and_(
                    User.telegram_id == user_tg_id,
                    Subscription.activation_code_id == activation_code_id,
                )
            )
        )

        subscription: Subscription | None = await self._session.scalar(query)

        if not subscription:
            return None

        return convert_db_model_to_subscription_dto(subscription)

    @exception_mapper
    async def get_subscriptions(self) -> list[SubscriptionDTO]:
        """Получаем все доступные подписки"""

        res = await self._session.scalars(select(Subscription))
        subscriptions: list[Subscription] = list(res)

        return [
            convert_db_model_to_subscription_dto(subscription)
            for subscription in subscriptions
        ]

    @exception_mapper
    async def get_expiring_subscriptions(self) -> list[SubscriptionDTO]:
        """Получаем подписки с истекающим сроке действия"""

        date_now = get_date_time()

        query = select(Subscription).where(
            and_(
                Subscription.status == SubscriptionStatus.active,
                Subscription.user_id != None,
                Subscription.end_date <= date_now + timedelta(days=1),
                ~Subscription.notify_sent,
            )
        )

        res = await self._session.scalars(query)
        subscriptions: list[Subscription] = list(res)

        return [
            convert_db_model_to_subscription_dto(subscription)
            for subscription in subscriptions
        ]

    @exception_mapper
    async def get_expired_subscriptions(self) -> list[SubscriptionDTO]:
        """Получаем закончившиеся подписки"""

        date_now = get_date_time()

        query = select(Subscription).where(
            and_(
                Subscription.status == SubscriptionStatus.active,
                Subscription.user_id != None,
                Subscription.end_date < date_now,
            ),
        )

        res = await self._session.scalars(query)
        subscriptions: list[Subscription] = list(res.unique())

        return [
            convert_db_model_to_subscription_dto(subscription)
            for subscription in subscriptions
        ]


class SubscriptionRepoImpl(SQLAlchemyRepo):
    @exception_mapper
    async def acquire_subscription_by_id(self, subscription_id: int):
        query = select(Subscription).where(Subscription.id == subscription_id)

        subscription: Subscription | None = await self._session.scalar(query)

        return subscription

    @exception_mapper
    async def create_subscription(self, subscription: Subscription):
        self._session.add(subscription)
        try:
            await self._session.flush((subscription,))
        except IntegrityError as err:
            self._parse_error(err)

    @exception_mapper
    async def update_subscription(self, subscription: Subscription) -> None:
        try:
            await self._session.merge(subscription)
        except IntegrityError as err:
            self._parse_error(err)

    @staticmethod
    def _parse_error(err: DBAPIError) -> None:
        """Определение ошибки"""

        raise RepoError from err
